﻿using System;
using System.IO;
using System.Net;
using System.Text;
using my_proxy.Utils;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace my_proxy.RequestHandlers
{
    /// <summary>
    ///  Handle one request of the client from the server.
    /// </summary>
    public class OpenRequestHandler
    {
        protected HttpListenerContext Context;
        protected FileReader FileReader;
        protected object FileLock;
        protected Configuration Conf;
        protected List<string> blackList;



        /// <summary>
        /// Initialize the communication with the Remote Server - Open mode
        /// </summary>
        /// <param name="cf">The configuration file</param>
        /// <param name="context">The context of the request</param>
        /// <param name="fr">The file reader object</param>
        /// <param name="fileLock">A lock for synchonization</param>
        public OpenRequestHandler(Configuration cf,
            HttpListenerContext context, FileReader fr,object fileLock)
        {
            this.Context = context;
            this.RemoteUrl = this.Context.Request.RawUrl;
            this.FileReader = fr;
            this.FileLock = fileLock;
            this.Conf = cf;
            if (context.Request.RemoteEndPoint != null)
            {
                Log.Info(context.Request.RemoteEndPoint.Address + " " + context.Request.RawUrl);
            }
            else
            {
                string contextErrMsg = "Context error in: " + context.Request.RawUrl;
                Log.Info(contextErrMsg);
                throw new Exception(contextErrMsg);
            }

            blackList = this.FileReader.ReadList(this.Conf.BlackFile);
       //     SafeHeaders = new List<string> { "Content-Length", "Date", "Host", "If-Modified-Since", "Range", "Referer"};
        }

        /// <summary>
        /// Main method - Method calls when client request the server
        /// </summary>
        public void ProcessRequest()
        {
            // Create a request with same data in navigator request
            HttpWebRequest request = this.GetRequest();
            if (request == null)
                return;
            try
            {
            //    string result = "";
                // Send the request to the remote server and return the response
                HttpWebResponse response = (HttpWebResponse)this.GetResponse(request);
                if (response != null)
                {   // if the server return a response - manipulate it (add cookies, etc.) 
                    // and send it back to the client
                    byte[] responseData = this.GetResponseStreamBytes(response);
                    // transfer cookies to the client from the server...
           
                    // send the response back to the client
                    this.SendResponse(response, responseData);
                }
            }
            catch (Exception)
            {
                Console.WriteLine("Did not complete handling request");
            }
        }


        /// <summary>
        /// Return address to communicate to the remote server
        /// </summary>
        public string RemoteUrl { get; protected set; }

        /// <summary>
        /// Create a request the remote server
        /// </summary>
        /// <returns>Request to send to the server </returns>
        protected virtual HttpWebRequest GetRequest()
        {
            if (this.Context.Request.HttpMethod == "GET")
            {
                if(this.RemoteUrl.Contains("?")){
                    string conURL = this.RemoteUrl.Substring(0, this.RemoteUrl.IndexOf('?'));
                    string param = this.RemoteUrl.Substring(this.RemoteUrl.IndexOf('?') + 1);
                    param = replaceOccureOfWord(new StringBuilder(param)).ToString();
                    this.RemoteUrl = conURL + "?" + param;
                }           
            }
            // Create a request to the server
            HttpWebRequest requestToServer = (HttpWebRequest)WebRequest.Create(this.RemoteUrl);
         
            // Set some options
            requestToServer.Method = this.Context.Request.HttpMethod;
            requestToServer.UserAgent = this.Context.Request.UserAgent;
            requestToServer.KeepAlive = requestToServer.KeepAlive;
            requestToServer.Proxy = null;
            
            requestToServer.Headers.Add("x-fordward-for:" + this.Context.Request.RemoteEndPoint.Address);
            requestToServer.Headers.Add("proxy-version:"+ "1.0" ); // server version
            requestToServer.CookieContainer = new CookieContainer(); 
            requestToServer.CookieContainer.Add(this.Context.Request.Url, this.Context.Request.Cookies);
            
            // For POST, write the post data extracted from the incoming request
            if (requestToServer.Method == "POST")
            {
                const int bufferSize = 1024;
                Stream clientStream = this.Context.Request.InputStream;
                byte[] clientPostData = new byte[bufferSize];
                requestToServer.ContentType = this.Context.Request.ContentType;
                int read = clientStream.Read(clientPostData, 0, bufferSize);
                StringBuilder data = new StringBuilder();
                Stream stream = requestToServer.GetRequestStream();
                int mRead = 0;
                //read data untile the end of the stream and write it to the rquest sended
                while (read > 0)
                {
                    StringBuilder tempString = new StringBuilder(Encoding.UTF8.GetString(clientPostData, 0, clientPostData.Length));
                    data.Append(tempString); 
                    read = clientStream.Read(clientPostData, 0, bufferSize);
                }
                // replacing entire data at once
                data = replaceOccureOfWord(data);
                mRead = data.Length;
                byte[] clientPostData2 = new byte[mRead];
                System.Text.Encoding encoding= System.Text.Encoding.UTF8;
                clientPostData2 = encoding.GetBytes(data.ToString());
                while (mRead > 0)
                {
                    stream.Write(clientPostData2, 0, mRead);
                    mRead = clientStream.Read(clientPostData, 0, bufferSize);
                }
                stream.Close();
                
                //TODO: check for mails in the request (use data)
                if (checkForMail(data))
                {
                    ProxyListener.forbbiden(Context);
                }
            }

            //check for mails in the request headers
            for (int i = 0; i < requestToServer.Headers.Count; i++)
            {
                StringBuilder m_header = new StringBuilder(requestToServer.Headers[i]);
                if (checkForMail(m_header))
                {    // checks for Mails in the headers
                    ProxyListener.forbbiden(Context);
                }
                
                // the Header replacement
                if (!WebHeaderCollection.IsRestricted(requestToServer.Headers.AllKeys[i].ToString())) //SafeHeaders.Contains(requestToServer.Headers.AllKeys[i].ToString()))
                    requestToServer.Headers.Set(requestToServer.Headers.AllKeys[i].ToString(), replaceOccureOfWord(m_header).ToString());
            }        
            return requestToServer;
        }

        protected StringBuilder replaceOccureOfWord(StringBuilder data)
        {
            data.Replace("%0D%0A","+");     // making sure "enter" doesn't mess-up the data
            string ans = data.ToString();           
            ans = replaceSE(ans);
            ans = replaceISE(ans);
            ans = replaceBGU(ans);
            return new StringBuilder(ans);
        }
   
        private string replaceSE(string before)
        {
            string ans="";
            int counter = 0;
            string[] s1 = before.Split(new string[] { "se", "SE", "Se", "sE" }, StringSplitOptions.None);
            for (int i = 0; i < s1.Length; i++)
            {
                counter = counter + s1[i].Length;
                if (i == 0 && s1[i].Length == 0)
                {
                    if (s1.Length == i + 1 )
                        break; 
                    if ((s1.Length > i + 1 && (s1[i + 1].Length == 0 || s1[i + 1].Length > 0 && !IsLetter(s1[i + 1][0]))))
                    { ans = ans + "SoftwareEngineering"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + "->" + "SoftwareEngineering"); }
                }
                else if (s1[i].Length > 0 && !IsLetter(s1[i][s1[i].Length - 1]) && s1.Length > i + 1 && s1[i + 1].Length > 0 && !IsLetter(s1[i + 1][0]))
                { ans = ans + s1[i] + "SoftwareEngineering";  Log.Info("Word transformed:" + before[counter] + before[counter + 1] + "->" + "SoftwareEngineering"); }
                else if (i == s1.Length - 2 && s1[i + 1].Length == 0 && s1[i].Length > 0 && !IsLetter(s1[i][s1[i].Length - 1]))
                { ans = ans + s1[i] + "SoftwareEngineering"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + "->" + "SoftwareEngineering"); }

                // the concatenation
                else if (i == s1.Length - 1)
                    ans = ans + s1[i];
                else
                    ans = ans + s1[i] + before[counter] + before[counter + 1];
                // every iteration
                counter += 2;
            }
            return ans;
        }

        private string replaceISE(string before)
        {
            
            string ans = "";
            int counter = 0;
            string[] s1 = before.Split(new string[] { "Ise", "ISE", "ISe", "IsE","iSE","isE","ise","iSe" }, StringSplitOptions.None);
            for (int i = 0; i < s1.Length; i++)
            {
                counter = counter + s1[i].Length;
                if (i == 0 && s1[i].Length == 0)
                {
                    if (s1.Length == i + 1)
                        break; 
                    if ((s1.Length > i + 1 && (s1[i + 1].Length == 0 || s1[i + 1].Length > 0 && !IsLetter(s1[i + 1][0]))))
                    { ans = ans + "InformationSystemEngineering"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + before[counter + 2] + "->" + "InformationSystemEngineering"); }
                }
                else if (s1[i].Length > 0 && !IsLetter(s1[i][s1[i].Length - 1]) && s1.Length > i + 1 && s1[i + 1].Length > 0 && !IsLetter(s1[i + 1][0]))
                {  ans = ans + s1[i] + "InformationSystemEngineering"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + before[counter + 2] + "->" + "InformationSystemEngineering"); }
                else if (i == s1.Length - 2 && s1[i + 1].Length == 0 && s1[i].Length > 0 && !IsLetter(s1[i][s1[i].Length - 1]))
                { ans = ans + s1[i] + "InformationSystemEngineering"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + before[counter + 2] + "->" + "InformationSystemEngineering"); }

                // the concatenation
                else if (i == s1.Length - 1)
                    ans = ans + s1[i];
                else
                    ans = ans + s1[i] + before[counter] + before[counter + 1]+ before[counter + 2];
                // every iteration
                counter += 3;
            }
            return ans;
        }

        private string replaceBGU(string before)
        {           
            string ans = "";
            int counter = 0;
            string[] s1 = before.Split(new string[] { "bgu", "Bgu", "BGu", "BgU","BGU","bGU","bgU","bGu" }, StringSplitOptions.None);
            for (int i = 0; i < s1.Length; i++)
            {
                counter = counter + s1[i].Length;
                if (i == 0 && s1[i].Length == 0)
                {
                    if (s1.Length == i + 1)
                        break;
                    { ans = ans + "BenGurion"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + before[counter + 2] + "->" + "BenGurion"); }
                }
                else if (s1[i].Length > 0 && !IsLetter(s1[i][s1[i].Length - 1]) && s1.Length > i + 1 && s1[i + 1].Length > 0 && !IsLetter(s1[i + 1][0]))
                { ans = ans + s1[i] + "BenGurion"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + before[counter + 2] + "->" + "BenGurion"); }
                else if (i == s1.Length - 2 && s1[i + 1].Length == 0 && s1[i].Length > 0 && !IsLetter(s1[i][s1[i].Length - 1]))
                { ans = ans + s1[i] + "BenGurion"; Log.Info("Word transformed:" + before[counter] + before[counter + 1] + before[counter + 2] + "->" + "BenGurion"); }

                // the concatenation
                else if (i == s1.Length - 1)
                    ans = ans + s1[i];
                else
                    ans = ans + s1[i] + before[counter] + before[counter + 1]+ before[counter + 2];
                // every iteration
                counter += 3;
            }
            return ans;
        }


        private bool IsLetter(char c)
        {
            return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
        }

        /// <summary>
        /// This function check for mail in data while using Regex
        /// </summary>
        /// <param name="data"></param>
        /// <returns> true - if contain email address, false otherwise</returns>
        protected bool checkForMail(StringBuilder data)
        {
            Regex emailRegex = new Regex(@"\w+([-+.]\w+)*[@,%40]\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.IgnoreCase);
            //find items that matches with our pattern
            MatchCollection emailMatches = emailRegex.Matches(data.ToString());
            for (int i = 0; i < emailMatches.Count; i++)            
                Log.Info("Email blocked:"+emailMatches[i].Value.Replace("%40","@"));
            if (emailMatches.Count > 0)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Send the request to the remote server and return the response
        /// </summary>
        /// <param name="request">Request to send to the server </param>
        /// <returns>Response received from the remote server
        ///           or null if page not found </returns>
        private HttpWebResponse GetResponse(HttpWebRequest request)
        {
            HttpWebResponse response;      
            try
            {
                response = (HttpWebResponse)request.GetResponse();
            }
            catch (WebException)
            {
                // Send 404 to client 
                this.Context.Response.StatusCode = 404;
                this.Context.Response.StatusDescription = "Page Not Found";
                //this.Context.Response("Page not found");
                this.Context.Response.Close();
                Console.WriteLine("404: Page Not Found");
                return null;
            }

            return response;
        }

    

        /// <summary>
        /// Return the response in bytes array format
        /// </summary>
        /// <param name="response">Response received
        ///             from the remote server </param>
        /// <returns>Response in bytes </returns>
        private byte[] GetResponseStreamBytes(HttpWebResponse response)
        {
            const int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            MemoryStream memoryStream = new MemoryStream();
            //get the response
            Stream responseStream = response.GetResponseStream();
            //read all the data from the response and save it in a memory stream
            if (responseStream != null)
            {
                int remoteResponseCount = responseStream.Read(buffer, 0, bufferSize);

                while (remoteResponseCount > 0)
                {
                    memoryStream.Write(buffer, 0, remoteResponseCount);
                    remoteResponseCount = responseStream.Read(buffer, 0, bufferSize);
                }
            }
            else
            {
                throw new Exception("Response is null");
            }

            //check for mails in the data and headers of the response
            StringBuilder sb = new StringBuilder();
            byte[] responseData = memoryStream.ToArray();
            string tempString = Encoding.UTF8.GetString(responseData, 0, responseData.Length);
            sb.Append(tempString);

            // TODO: email checking

            // close the open streams
            memoryStream.Close();
            responseStream.Close();
            memoryStream.Dispose();
            responseStream.Dispose();

            return responseData;
        }

        /// <summary> 
        ///  send the response back to the client
        /// </summary>
        /// <param name="response"> the response to send back </param>
        /// <param name="responseData"> the response data to send back </param>
        private void SendResponse(HttpWebResponse response, byte[] responseData)
        {
            // Send the response to client
            this.Context.Response.ContentEncoding = Encoding.UTF8;
            this.Context.Response.ContentType = response.ContentType;
            try
            {
                this.Context.Response.OutputStream.Write(responseData, 0, responseData.Length);
                this.Context.Response.Close();
            }
            catch (Exception)
            {
                Console.WriteLine("could not finish proxy request");
            }
        }

    }
}
